Sophie

Sophie

distrib > Mandriva > 2010.2 > i586 > media > contrib-release-src > by-pkgid > 54cd66b871e978c39b0d2081155e1850 > files > 3

apache-mod_i18n-0-8mdv2010.1.src.rpm

/* Copyright 2006 Joachim Zobel <jz-2006@heute-morgen.de>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * mod_i18n implements Zopes i18n namespace in an output filter. It thereby
 * allows gettext based internationalization of arbitray html content 
 * delivered by apache.
 *
 * The i18n attribute that are currently implemented are:
 * translate, name, domain, target
 * attribute will soon follow.
 *
 * Requirements
 *    mod_xml2 is required for parsing the html. It has to be loaded 
 *      before mod_xml2.
 *    The prefork mpm has to be used, since GNU gettext is not threadsafe.
 *
 * It is compiled and installed as expected with
 * /usr/local/apache2/bin/apxs  -i -c -I /usr/include/libxml2 mod_i18n.c \
 *    /usr/local/lib/libxml2.la /usr/lib/libapreq2.la
 *
 * Configuration
 *
 * The name of the filter is i18n. The sax filter implemented by mod_xml2
 * has to be run before.
 *
 * Configuration Directives
 *
 * I18NLocaleDir <directory>
 * Context: server config, virtual host, directory, .htaccess
 * The directory that holds the locale directories used by gettext.
 *
 * I18NLangParam <name>
 * Context: server config, virtual host, directory, .htaccess
 * The GET parameter that is used to pass the language code to the i18n
 * filter.
 *
 * I18NLangParamForce
 * Context: server config, virtual host, directory, .htaccess
 * The language set by a request parameter takes precedence over the 
 * language set by target attributes.
 *
 */

/*
 * Choosing a language
 *
 * There are 2 ways to choose a language. Both take a language
 *    tag as defined by RFC 2616 (thats what is send with 
 *    Accept-Language). 
 * 1. You can configure a GET parameter name to use to
 *    select a language with I18NLangParam. 
 * 2. By an i18n target attribute.
 * You can interface with content negatiation by including 
 * negotiated target attributes with SSI.
 * By default target tags have priority over the language
 * parameter. This can be overriden with I18NLangParamForce.
 */


#include <apr.h>
#include <apr_lib.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_buckets.h>
#include <apr_uri.h>
#include <apr_strings.h>
#include <apr_hash.h>
#include <apr_file_info.h>

#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_request.h>
#include <http_log.h>
#include <ap_regex.h>

#include <apreq2/apreq.h>
#include <apreq2/apreq_param.h>

#include <xlocale.h>
#include <libintl.h>
#include <locale.h>

#include <libxml/tree.h>
// #include <libxml/xmlerror.h>
// #include <libxml/parser.h>

module AP_MODULE_DECLARE_DATA i18n_module;

#include "frag_buffer.h"
#include "buckets_sax.h"
#include "sax_util.h"
#include "tree_transform.h"

// Parameters for the transform filter
typedef struct
{
    apr_pool_t *pool;
    // request to use for logging
    request_rec *r_log;
    // The directury where the locale is
    const char *locale_dir;
    // locale for gettext 
    const char *locale;
    // The current domain
    const xml_char_t *domain;
} param_transform;

// module configuration
typedef struct
{
    // The locale directory for gettext.
    const char *locale_dir;
    // The name of the request parameter to set the language.
    const char *lang_param;
    // The lang. param. overrides
    int lang_param_force;
} i18n_cfg;

// The filter context
typedef struct
{
    // The current translate bucket,
    // 0 if there is none.
    se_id_t translate;

    // The domains stack
    apr_array_header_t *domains;
    // domains are unified
    unq_set_t *unq_domain;

    // The targets stack
    apr_array_header_t *targets;
    // domains are unified
    unq_set_t *unq_target;

    // The tree transformation filter.
    ap_filter_t *f_tr;
    // The transformation filters parameters
    param_transform *param;

    // The outbound brigade
    apr_bucket_brigade *bb_out;

    // A pool that si cleaned between ap_pass_brigade
    // calls.
    // apr_pool_t *p_tmp;

    // The sax context used to create 
    // sax buckets from transformed trees.
    sax_ctx *sax;

    // Buckets that are needed 
    // frequently to turn document
    // fragments into complete documents.
    apr_bucket *b_xml_decl;
} i18n_ctx;

// start tag related stack entries 
typedef struct
{
    // The target start tag
    se_id_t se_id;
    // The target
    const xml_char_t *tag_value;
} i18n_tag_entry;

static const xml_char_t NS_I18N[] = "http://xml.zope.org/namespaces/i18n";

/*****************************************************************************
 * Tree Transformation Functions
 *****************************************************************************/

static void i18n_translate_node(param_transform * param, xmlNodePtr nd);
static void i18n_expand_text_to_nodes(param_transform * param, xmlNodePtr nd,
                                      apr_hash_t * tr_chld_nd, xmlNodePtr bef,
                                      const xml_char_t * trans);
static const xml_char_t *i18n_extract_text(param_transform * param,
                                           xmlNodePtr nd,
                                           apr_hash_t * tr_chld_nd);
static xml_char_t *i18n_normalize_ws(xml_char_t * dest, const xml_char_t *);

static const xml_char_t *i18n_gettext(param_transform * param,
                                      const char *msgid);

/*
 * function:  i18n_transform
 * description: The funtion that is passed to tree_transform.c
 * parameters: ??
 * return: -
 */
static apr_status_t i18n_transform(void *param, xmlDocPtr doc)
{
    param_transform *ptr = param;
    sax_check_pool(ptr->pool);
    sax_check_pool(ptr->r_log->pool);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ptr->r_log,
                  "i18n_transform called for domain %s.", ptr->domain);
    xmlNodePtr nd = xmlDocGetRootElement(doc);
    i18n_translate_node(ptr, nd);
    sax_check_pool(ptr->r_log->pool);
    sax_check_pool(ptr->pool);
}

/**
 * function:  i18n_translate_node
 * description: Translates the given node and all its children
 * parameters: nd - a node with an translate or name 
 *      attribute
 * return: -
 */

static void i18n_translate_node(param_transform * param, xmlNodePtr nd)
{
    apr_pool_t *pool = param->pool;
    sax_check_pool(param->r_log->pool);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                  "i18n_translate_node called.");

    const xml_char_t *trans = xmlGetNsProp(nd, "translate", NS_I18N);
    const xml_char_t *name = xmlGetNsProp(nd, "name", NS_I18N);
    if (trans || name) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                      "Trying to translate %s.", trans);

        apr_hash_t *tr_chld_nd = apr_hash_make(pool);
        // extract text also translates the child nodes
        const xml_char_t *xt = i18n_extract_text(param, nd, tr_chld_nd);
        if (trans && !trans[0]) {
            // The extracted text is only used if 
            // there was an empty attribute.
            trans = xt;
        }
        if (trans) {
            trans = i18n_gettext(param, trans);
            xmlRemoveProp(xmlHasNsProp(nd, "translate", NS_I18N));
        }
        // We add a comment node to prevent merging of adjacent text nodes.
        xmlNodePtr nd_start_old =
            xmlAddPrevSibling(nd->children, xmlNewComment("seperator"));
        i18n_expand_text_to_nodes(param, nd, tr_chld_nd, nd_start_old, trans);

        // remove the old stuff 
        while (nd_start_old->next) {
            xmlNodePtr cur = nd_start_old->next;
            xml_char_t *cont = xmlNodeGetContent(cur);
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                          "Unlinking node '%s'.", cont);
            xmlUnlinkNode(cur);
            xmlFreeNode(cur);
        }
        xml_char_t *cont = xmlNodeGetContent(nd_start_old);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                      "Unlinking node '%s'.", cont);
        xmlFree(cont);
        xmlUnlinkNode(nd_start_old);
        xmlFreeNode(nd_start_old);
    }
    else {
        // Try children
        xmlNodePtr chld;
        for (chld = nd->children; chld; chld = chld->next) {
            i18n_translate_node(param, chld);
        }
    }
    sax_check_pool(param->r_log->pool);
}

/*
 * function:  i18n_expand_text_to_nodes
 * description: Expands the given text to child nodes of oNd,
 * 		replacing all $ variables with the corresponding
 *		nodes from oTrCh.
 * parameters:  oNd - a node with an translate or name 
 *             	attribute
 *	        oTrCh - a name -> node map 
 *	        oBef - the insertBefore marker
 *		sTrans - The text to expand
 * return: -
 */
static void i18n_expand_text_to_nodes(param_transform * param, xmlNodePtr nd,
                                      apr_hash_t * tr_chld_nd, xmlNodePtr bef,
                                      const xml_char_t * trans)
{
    apr_pool_t *pool = param->pool;
    sax_check_pool(param->r_log->pool);
    static ap_regex_t rx = { NULL, 0, 0 };
    if (!rx.re_pcre) {
        ap_regcomp(&rx, "\\${[^}]+}|\\$\\w+", 0);
    }
    if (!trans) {
        return;
    }
    const xml_char_t *run = trans;
    ap_regmatch_t match = { 0, 0 };
    int len = 0;
    while (!ap_regexec(&rx, run, 1, &match, 0)) {
        const char *start = run;
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                      "Creating text node for %d of '%s'.", match.rm_so, run);
        xmlAddPrevSibling(bef, xmlNewTextLen(run, match.rm_so));
        run += match.rm_so + 1;
        len = match.rm_eo - match.rm_so - 1;
        if (run[0] == '{') {
            run++;
            len -= 2;
        }

        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                      "Looking up node named by %d of >%.10s from hash.", len,
                      run);
        xmlNodePtr nnd = apr_hash_get(tr_chld_nd, run, len);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                      "Adding node %x named %s from hash.", nnd,
                      xmlGetNsProp(nnd, "name", NS_I18N));
        xmlAddPrevSibling(bef, nnd);

        xmlRemoveProp(xmlHasNsProp(nnd, "name", NS_I18N));
        run = start + match.rm_eo;
    }

    // There is one more text
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                  "Creating text node for '%s'.", run);
    xmlAddPrevSibling(bef, xmlNewText(run));
    sax_check_pool(param->r_log->pool);
}

/*****************************************************************************
 * Content negotiation
 *****************************************************************************/
/*
 * UNUSED
 *
 * Retrieve a list of available languages from the gettext locale directory.
 * @param pool - for memory allocation
 * @param dirlist - the list to return - an arra of char *
 * @param dirname - the gettext locale dir
 * @return APR_SUCCESS on success.
 */
static apr_status_t i18n_list_languages(apr_pool_t * pool,
                                        apr_array_header_t * dirlist,
                                        const char *dirname)
{
    apr_dir_t *dirp;
    apr_finfo_t dirent;

    // reset dirlist
    dirlist->nelts = 0;

    if (apr_dir_open(&dirp, dirname, pool) != APR_SUCCESS) {
        ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
                      "locale dir %s could not be opened.", dirname);
        return HTTP_FORBIDDEN;
    }

    while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
        // ignore all non-dirs
        if (!(dirent.valid & APR_FINFO_TYPE) || (dirent.filetype != APR_DIR)) {
            continue;
        }

        char **dirnamep = apr_array_push(dirlist);
        *dirnamep = apr_pstrdup(pool, dirent.name);
        // Relpacing _ with - is all that is needed
        // to create language tags from gettext 
        // locale directories.
        char *p;
        for (p = *dirnamep; *p; p++) {
            if (*p == '_') {
                *p = '-';
            }
        }
    }

    apr_dir_close(dirp);

    return APR_SUCCESS;
}

/*
 * function:  i18n_extract_text
 * description: Extract text for translation
 * parameters: nd - a node with a translate or name 
 *                attribute
 *             tr_chld_nd - Translated Children returns the
 *                translated child nodes by name
 * return: The extracted text. nodes with name attr. are replaced 
 *      with ${name}
 */

static const xml_char_t *i18n_extract_text(param_transform * param,
                                           xmlNodePtr nd,
                                           apr_hash_t * tr_chld_nd)
{
    apr_pool_t *pool = param->pool;
    sax_check_pool(param->r_log->pool);
    frag_buffer_t *rtn = frag_create(pool);
    // We iterate over the child nodes
    xmlNodePtr chld;
    for (chld = nd->children; chld; chld = chld->next) {

        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                      "Extracting node of type %d.", chld->type);
        switch (chld->type) {
        case XML_TEXT_NODE:
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                          "Extracting text node %s.", chld->content);
            frag_write(rtn, chld->content, strlen(chld->content));
            break;
        case XML_ELEMENT_NODE:
            {
                const xml_char_t *name = xmlGetNsProp(chld, "name", NS_I18N);
                if (name) {
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                                  "Extracting element node %s.", name);
                    i18n_translate_node(param, chld);

                    frag_write(rtn, "${", 2);
                    frag_write(rtn, name, strlen(name));
                    frag_write(rtn, "}", 1);

                    apr_hash_set(tr_chld_nd, name, APR_HASH_KEY_STRING, chld);
                }
                else {
                    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, param->r_log,
                                  "Child node without i18n name found in translate.");
                }
                break;
            }
        default:
            break;
        }
    }

    // XXX: Normalize Whitespace
    const apr_off_t len = frag_length(rtn);
    xml_char_t *buf = apr_palloc(pool, len + 1);
    frag_to_buffer(rtn, 0, buf, len);
    buf[len] = '\0';

    i18n_normalize_ws(buf, buf);
    return buf;
}

static xml_char_t *i18n_normalize_ws(xml_char_t * dest,
                                     const xml_char_t * src)
{
    // remove leading blank
    int was_space = 1;
    while (*src) {
        if (!apr_isspace(*src)) {
            *dest++ = *src;
            was_space = 0;
        }
        else if (!was_space) {
            *dest++ = ' ';
            was_space = 1;
        }
        ++src;
    }
    if (was_space)
        // remove trailing blank
        *(--dest) = 0;
    else
        *dest = 0;
    return (dest);
}

// This does not work yet    
// dgettext_l(domain, msgid, mylocale)

static const xml_char_t *i18n_gettext(param_transform * param,
                                      const char *msgid)
{
    sax_check_pool(param->r_log->pool);
    bindtextdomain(param->domain, param->locale_dir);
    bind_textdomain_codeset(param->domain, "UTF-8");

    char *oldlocale = setlocale(LC_MESSAGES, NULL);
    setlocale(LC_MESSAGES, param->locale);

/*  uselocale would be threadsafe, but does not work
    locale_t oldloc = uselocale(NULL);
    locale_t newloc = newlocale(LC_MESSAGES_MASK, param->domain, NULL);
    uselocale(newloc);
*/
    const char *rtn = dgettext(param->domain, msgid);
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                  "i18n_gettext called for '%s' and '%s' from '%s':'%s', found '%s'.",
                  param->domain, param->locale, param->locale_dir, msgid,
                  rtn);
/*    
    uselocale(oldloc);
    //freelocale(newloc);
*/
    setlocale(LC_MESSAGES, oldlocale);

    return rtn;
}


/*
static const xml_char_t *i18n_gettext(param_transform * param, const char *msgid)
{
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, param->r_log,
                  "i18n_gettext called for %s.", msgid);
    return "Dummy translation";
}
*/

/*****************************************************************************
 * Module Handlers
 *****************************************************************************/

static unq_set_t *unq_locale = NULL;
// only used to unify locales
static apr_pool_t *p_ul = NULL;

static void i18n_child_init(apr_pool_t * p, server_rec * s)
{
    p_ul = p;
    unq_locale = apr_table_make(p, 10);
}

// BAUSTELE
const int L_BUF_SZ = 20;

static const xml_char_t *i18n_locale_from_lang(const char *lang)
{
    char locale[L_BUF_SZ + 1];
    if (!lang) {
        return NULL;
    }

    strncpy(locale, lang, L_BUF_SZ);
    locale[L_BUF_SZ] = '\0';
    // Replace - with _
    char *p = strchr(locale, '-');
    if (p) {
        *p = '_';
    }

    return sax_unify_name(p_ul, unq_locale, locale);
}

/*
 * i18n_filter_init
 */

static int i18n_filter_init(ap_filter_t * f)
{
    // Workaround: filter_init can get multiple calls.
    if (f->ctx) 
        return OK;

    apr_pool_t *pool = f->r->pool;
    i18n_cfg *cfg = ap_get_module_config(f->r->per_dir_config, &i18n_module);
    i18n_ctx *fctx = f->ctx = apr_pcalloc(pool, sizeof(i18n_ctx));
    memset(fctx, 0, sizeof(i18n_ctx));
    // fctx->starts = sax_start_stack_init(pool);

    fctx->domains = apr_array_make(pool, 5, sizeof(i18n_tag_entry));
    fctx->unq_domain = apr_table_make(pool, 5);

    if (cfg->lang_param_force) {
        // target attributes are ignored
        fctx->targets = NULL;
        fctx->unq_target = NULL;
    }
    else {
        fctx->targets = apr_array_make(pool, 5, sizeof(i18n_tag_entry));
        fctx->unq_target = apr_table_make(pool, 5);
    }

    fctx->bb_out = apr_brigade_create(pool, f->c->bucket_alloc);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
                  "i18n_filter_init called.");
    xml2_tree_log_filter_chain(APLOG_MARK, f);

    param_transform *param = fctx->param =
        apr_pcalloc(pool, sizeof(param_transform));
    apr_pool_create(&param->pool, pool);
    apr_pool_tag(param->pool, "i18n-param");
    param->r_log = f->r;
    param->domain = NULL;

    // Fill locale dir. from config
    param->locale_dir = cfg->locale_dir;

    // Read the language from the request
    apr_table_t *vars = apr_table_make(pool, 6);
    if (f->r->args
        && apreq_parse_query_string(pool, vars, f->r->args) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r,
                      "Failed to parse query string.");
    }
    // BAUSTELLE
    const char *locale =
        i18n_locale_from_lang(apr_table_get(vars, cfg->lang_param));
    param->locale = locale;

    fctx->f_tr = transform_filter_create(f, NULL, i18n_transform, param);

    sax_check_pool(param->r_log->pool);

    return OK;
}

/**
 * The desired filter chain is as follows:
 * 1.  Streaming XPath for @i18n:*
 * 2.  Attribute switch
 * 2.a Tree transform for everything inside i18n::translate
 * 2.b i18n actions for the rest
 *
 */


/*
 * i18n_filter
 */
static int i18n_filter(ap_filter_t * f, apr_bucket_brigade * bb)
{
    apr_bucket *b;
    apr_status_t rv = APR_SUCCESS;
    i18n_ctx *fctx = f->ctx;
    apr_bucket_brigade *bb_out = fctx->bb_out;
    apr_pool_t *pool = f->r->pool;
    const xml_char_t *unq_i18n_ns = NULL;
    const xml_char_t *unq_translate = NULL;
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "i18n_filter called.");
    xml2_tree_log_filter_chain(APLOG_MARK, f);

    transform_filter_y_connect(fctx->f_tr, f);

    // All buckets are moved to bb_out. bb_out is always passed
    // before leaving the filter. It is only stored in the brigade for 
    // reuse.
    for (b = APR_BRIGADE_FIRST(bb);
         !APR_BRIGADE_EMPTY(bb); b = APR_BRIGADE_FIRST(bb)) {
        // There is no EOS handling
        sax_check_pool(fctx->param->r_log->pool);


        if (!BUCKET_IS_SAX(b)) {
            APR_BUCKET_REMOVE(b);
            APR_BRIGADE_INSERT_TAIL(bb_out, b);
            continue;
        }

        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "sax bucket found.");
        bucket_sax *bs = b->data;
        // Initialisations that can not be done in i18n_filter_init
        if (!fctx->sax) {
            // We need the first sax bucket, 
            // so he have to do this here.
            // Initialize the current filter sax bucket.
            fctx->sax = apr_pcalloc(f->r->pool, sizeof(sax_ctx));
            sax_ctx_init_again(fctx->sax, bs->bctx,
                               bs->mctx, bb_out, f, NULL);
            // Initialize the transform filters sax bucket.
            // Note that bb_out, f will not be used tfor this filter.
            transform_filter_set_sax(fctx->f_tr, fctx->sax);
        }
        if (!unq_i18n_ns) {
            all_unq_t unq = fctx->sax->bctx.unq;
            unq_i18n_ns = sax_unify_name(pool, unq.uri, NS_I18N);
            unq_translate = sax_unify_name(pool, unq.name, "translate");
        }
        if (sax_inspect_which(b) == XML_DECL) {
            apr_bucket_copy(b, &fctx->b_xml_decl);
        }

        ap_assert(bb_out == fctx->sax->bb);
        // i18n domain
        if (sax_inspect_which(b) == START_ELT) {
            const xml_char_t *domain =
                sax_pop_attr(b, "domain", NS_I18N, NULL);
            if (domain) {
                i18n_tag_entry *dm = apr_array_push(fctx->domains);
                dm->tag_value =
                    sax_unify_name(pool, fctx->unq_domain, domain);
                dm->se_id = sax_inspect_se_id(b);
                // Set the filters domain parameter 
                fctx->param->domain = dm->tag_value;
            }
            const xml_char_t *target =
                sax_pop_attr(b, "target", NS_I18N, NULL);
            if (fctx->targets && target) {
                i18n_tag_entry *tg = apr_array_push(fctx->targets);
                tg->tag_value =
                    sax_unify_name(pool, fctx->unq_target, target);
                tg->se_id = sax_inspect_se_id(b);
                // Set the filters target parameter 
                fctx->param->locale = i18n_locale_from_lang(tg->tag_value);
            }
        }
        else {
            i18n_tag_entry *dm = sax_stack_top(i18n_tag_entry, fctx->domains);
            if (dm && (sax_inspect_se_id(b) == dm->se_id)) {
                apr_array_pop(fctx->domains);
                // Set the filters domain parameter 
                dm = sax_stack_top(i18n_tag_entry, fctx->domains);
                fctx->param->domain = dm ? dm->tag_value : NULL;
            }
            if (fctx->targets) {
                i18n_tag_entry *tg =
                    sax_stack_top(i18n_tag_entry, fctx->targets);
                if (tg && (sax_inspect_se_id(b) == tg->se_id)) {
                    apr_array_pop(fctx->targets);
                    // Set the filters target parameter 
                    tg = sax_stack_top(i18n_tag_entry, fctx->targets);
                    fctx->param->locale =
                        tg ? i18n_locale_from_lang(tg->tag_value) : NULL;
                }
            }
        }

        // i18n detection
        se_id_t se_id = sax_inspect_ns(b, NS_I18N, NULL, 1);
        if (se_id > 0) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0,
                          f->r, "i18n (start) tag.");
            //  We check if we have entered a translate tag
            if (!fctx->translate) {
                if (sax_inspect_which(b) == START_ELT) {
                    start_elt_t *se = sax_inspect_event(b);
                    attr_t *attr = se->atts;
                    while (attr =
                           sax_extract_next_attr(attr,
                                                 unq_i18n_ns, NULL), attr) {
                        if (attr->name.uri == unq_i18n_ns) {
                            fctx->translate = se_id;
                            // we pass the buckets before b
                            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0,
                                          f->r, "Passing brigade to %s.", f->next->frec->name);
                            sax_check_pool(fctx->param->r_log->pool);
                            rv = ap_pass_brigade(f->next, bb_out);
                            if (rv != APR_SUCCESS) {
                                return rv;
                            }
                            apr_array_header_t *namespaces =
                                ((bucket_sax *) (b->data))->bctx->namespaces;
                            rv = transform_start_faked_doc(fctx->sax,
                                                           bb_out,
                                                           fctx->b_xml_decl,
                                                           namespaces);
                            if (rv != APR_SUCCESS) {
                                return rv;
                            }
                        }
                        attr++;
                    }
                }
            }
        }

        APR_BUCKET_REMOVE(b);
        APR_BRIGADE_INSERT_TAIL(bb_out, b);
        //  We check if we are leaving a translate tag
        if (fctx->translate) {
            if (sax_inspect_se_id(b) == -fctx->translate) {
                fctx->translate = 0;
                // we pass the buckets up to and including b

                apr_array_header_t *namespaces =
                    ((bucket_sax *) (b->data))->bctx->namespaces;
                rv = transform_end_faked_doc(fctx->sax, bb_out, namespaces);
                if (rv != APR_SUCCESS) {
                    return rv;
                }

                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0,
                              f->r, "Passing brigade to %s.", fctx->f_tr->frec->name);
                sax_check_pool(fctx->param->r_log->pool);
                rv = ap_pass_brigade(fctx->f_tr, bb_out);
                if (rv != APR_SUCCESS) {
                    return rv;
                }
            }
        }

        APR_BRIGADE_CHECK_CONSISTENCY(bb);
        APR_BRIGADE_CHECK_CONSISTENCY(bb_out);
    }
    sax_check_pool(fctx->param->r_log->pool);

    // We are done, so we pass the brigade 
    if (fctx->translate) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0,
                      f->r,
                      "Passing incomplete brigade to %s.", fctx->f_tr->frec->name);
        rv = ap_pass_brigade(fctx->f_tr, bb_out);
    }
    else {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "Passing brigade to %s.", f->next->frec->name);
        rv = ap_pass_brigade(f->next, bb_out);
    }
    // and cleanup
    
    // apr_brigade_cleanup(bb);
    /// param->pool clear - when?
    // apr_pool_clear(fctx->p_tmp);
    return rv;
}

/*****************************************************************************
 * Configuration
 *****************************************************************************/

static const char *i18n_locale_dir(cmd_parms * cmd, void *cf, const char *arg)
{
    i18n_cfg *cfg = cf;
    // Check if arg is a valid path?
    cfg->locale_dir = arg;


    ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0,
                  cmd->pool, "locale_dir in cfg set to %s.", arg);
    return NULL;
}

static const char *i18n_lang_param(cmd_parms * cmd, void *cf, const char *arg)
{
    i18n_cfg *cfg = cf;
/*  libapreq2 is 50k on linux, thats not wort the hassle.    
    static apr_dso_handle_t *dlhandle = NULL;
    apr_status_t rv;

    if (!dlhandle) {
#ifdef WIN32
      const char path = "libapreq2.dll";
#else
      const char path = "libapreq2.dll";
#endif

      rv = apr_dso_load(&dlhandle, path, pool);
      if (rv != APR_SUCCESS) { // APR_EDSOOPEN 
        return;
      }
    }

*/

    cfg->lang_param = arg;

    ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0,
                  cmd->pool, "lang_param in cfg set to %s.", arg);
    return NULL;
}

static const char *i18n_lang_param_force(cmd_parms * cmd, void *cf,
                                         const char *ignored)
{
    i18n_cfg *cfg = cf;

    cfg->lang_param_force = 1;

    ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0,
                  cmd->pool, "lang_param_force in cfg set.");

    return NULL;
}

static const command_rec i18n_cmds[] = {
    AP_INIT_TAKE1("I18NLocaleDir",
                  i18n_locale_dir, NULL, OR_ALL,
                  "The locale directory for gettext."),
    AP_INIT_TAKE1("I18NLangParam",
                  i18n_lang_param, NULL, OR_ALL,
                  "The name of the request parameter to set the language."),
    // AP_INIT_NO_ARG does not work
    AP_INIT_RAW_ARGS("I18NLangParamForce",
                     i18n_lang_param_force, NULL, OR_ALL,
                     "The language set by a request parameter takes priority."),
    {
     NULL}
};

static void *cr_i18n_cfg(apr_pool_t * pool, char *x)
{
    i18n_cfg *cfg = apr_pcalloc(pool, sizeof(i18n_cfg));
    cfg->locale_dir = NULL;
    cfg->lang_param = NULL;
    cfg->lang_param_force = 0;

    ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0,
                  pool, "cr_i18n_cfg called with %s.", x);
    return cfg;
}

static void *merge_i18n_cfg(apr_pool_t * pool, void *BASE, void *ADD)
{
    i18n_cfg *base = BASE;
    i18n_cfg *add = ADD;
    i18n_cfg *cfg = apr_palloc(pool, sizeof(i18n_cfg));

    ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0,
                  pool, "merge_i18n_cfg called for %s.", add->locale_dir);
    *cfg = *base;
    if (add->locale_dir) {
        cfg->locale_dir = add->locale_dir;
    }
    if (add->lang_param) {
        cfg->lang_param = add->lang_param;
    }
    if (add->lang_param_force) {
        cfg->lang_param_force = 1;
    }

    return cfg;
}

/*****************************************************************************
 * The usual module stuff
 *****************************************************************************/

static void i18n_hooks(apr_pool_t * p)
{
    ap_hook_child_init(i18n_child_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_register_output_filter("i18n",
                              i18n_filter,
                              i18n_filter_init, AP_FTYPE_RESOURCE);
}

module AP_MODULE_DECLARE_DATA i18n_module = {
    STANDARD20_MODULE_STUFF, cr_i18n_cfg,
    merge_i18n_cfg, NULL, NULL, i18n_cmds, i18n_hooks
};