Jump to content

Module: Languages (multi language content management)


Oliver
 Share

Recommended Posts

Try to insert the following line between line 587 and 588 before the if condition:

die("’#^".$this->config->urls->root."([a-z0-9\-_]+/).*$#’, ".$_SERVER['REQUEST_URI']);

Just to be sure, the hook definitely doesn’t work here.

Link to comment
Share on other sites

Really weird. But just to be sure: I made a mistake on my first upload to GitHub and pushed a file with a .module.php ending. Please check, that there aren’t two module files in your module directory, one .module, one .module.php. Also try to insert a die("something"); in the constructor somewhere to be sure, you are working on the right file.

Link to comment
Share on other sites

Good. One question answered. Anyone got an idea, why this hook shouldn’t work? It does on my test environment.

        $this->hooks['getErrorPage'] = $this->addHookAfter('ProcessPageView::pageNotFound', $this, 'getErrorPage');
Link to comment
Share on other sites

Good. One question answered. Anyone got an idea, why this hook shouldn’t work? It does on my test environment.

I'm not sure why that wouldn't be working either. It looks correct to me. I was originally wondering if there might be some condition where the 404 was getting thrown before your module was init()'d, but looking in the source I now see that's very unlikely. However, it is feasible if there is another 3rd party module involved that's getting init()'d and throwing it before your module is init()'d.

Nico, can you describe more details about how to reproduce the case you are getting? For instance, when debugging this stuff, I think it's best to start with a stock installation with the latest source, that doesn't have any other 3rd party modules installed. That way we can remove any other factors that may be accounting for the different behavior. Then make sure it can be reproduced there. If it can be, then check to see if there are any other factors that might help Oliver reproduce it (like URL in address bar, etc.)

Oliver, you may already be aware of this. But if you hook in before (rather than after) ProcessPageView::pageNotFound, you could change the value of $this->config->http404PageID to whatever Page ID you wanted. Of course, you could do that anytime before (like in your init) as well. Not sure if this is helpful in this case or not but just wanted to mention it.

Link to comment
Share on other sites

Ryan, thanks for that hint. That could be really helpful. I could set $this->config->http404 the moment the language is set through setLanguage(). I guess, this would also solve Nico’s problem, as the error hook wouldn’t be necessary anymore. But I would have to change the setLanguage hook from page::render to another event, on which a missing page wouldn’t have caused the error yet.

Link to comment
Share on other sites

Ryan, thanks for that hint. That could be really helpful. I could set $this->config->http404 the moment the language is set through setLanguage(). I guess, this would also solve Nico’s problem, as the error hook wouldn’t be necessary anymore. But I would have to change the setLanguage hook from page::render to another event, on which a missing page wouldn’t have caused the error yet.

If you were changing the value of $config->http404PageID, then you'd need to do it before ProcessPageView::pageNotFound. As you mentioned, Page::render just isn't useful in this case. A good place would be in your module's init() since init() is called before the $page is determined. You could also do it in a before hook to pageNotFound, but I don't think you've got any more useful information at that point than you do in init().

This is unrelated to the above, but Module::ready() is a good replacement for Page::render in many cases. It was recently added to the Module interface as an optional function, and it works very much like Module::init() but is called after the $page is determined rather than before. To use, add a ready() function to your module, and PW will automatically call that function when the $page is determined and available in the API, but before it is rendered. As a result, it is a good replacement for before(Page::render), and doesn't require a hook. But note that neither Page::render or Module::ready is going to be useful for handling 404s.

Link to comment
Share on other sites

I tried to install lates version and got some troubles. I unchecked the home_language template "can't move" setting and deleted the lang trees. After deleting the template itself, I was ready to reinstall the module. Now when installing I got error of duplicate entry in fieldgroups .. so I had to manually delete the home_language template/fieldgoup/field entries. After that it all went well. I created a "about us" page in /en/. Oh and it already has a hidden 404! Cool :) Ok, now I went to add new language, and selected clone from a existing language. Now I got a /de/ with two 404 inside, so It got one added and one cloned. :)  And I can't remove one of them anymore.

Regarding the languages template var array.

I tried to do a simple lang navigation that shows links to exisiting languages. ie ( DE | FR | EN )

I can do something like this which is cool:

<?php 

if(isset($languages)) {

foreach($languages->translations as $key => $trans){

	$class = $trans->url == $page->url ? " class='on'" : '';

	echo $pages->get($trans->url)->status; // url returns 1 on a unpublished page - ?
	echo $pages->get($trans->id)->status; // id returns 2049 on a unpublished page - ??

	if( !$trans->is(Page::statusUnpublished) ){
		echo "<a $class href='$trans->url'>".strtoupper($key)."</a> | ";
	}
}
}
?>

But it would have unpublished pages too, so I had to add a check. If a page in one language is unpublished, how are your plans on this?

Oh, this might something for Ryan. There a weird thingy I experienced when checking for page status. It returns something else when checking using id than with url.

	
echo $pages->get($trans->url)->status; // url returns 1 on a unpublished page - ?
echo $pages->get($trans->id)->status; // id returns 2049 on a unpublished page - ??

If you might find the time, Ryan, can you give some examples checking for the different page statuses in a new thread? Is $page->is(Page::statusUnpublished) the recommended one?

EDIT:

Don't know if you're aware or still working on.

When I create a new in /en/ and keep it unpublished, then go and create the translation for /de/ and select "create new page", then hit "save but keep unpublished" it creates the translation page in /de/ fine, but it's published. I think it should be unpublished too or always by default.

Link to comment
Share on other sites

Thanks a lot, Soma, for your inputs. It’s right, I forgot to exclude the error page from the cloning process on creation of a new language. Also keeping the new translations unpublished is good idea. Maybe I also should exclude unpublished pages from the translations array. I’ll try to work something out here.

And yeah, the uninstall process is still a mess. I know that. I’m not sure yet how to change this for the better.

Link to comment
Share on other sites

  • 2 weeks later...

With the upcoming multi-language support of PW2.2 in mind, I think, it’s needed to do some changes in my language module–like e.g. reacting to the fact, that the $languages template var is now already in use.

First–as it could be a reason for confusion–I’ll change  the modules name from "Languages" to … ehm, not sure yet. Something like "Content Translations".

Also will I make all texts in the admin translatable as well, of course.

There are some questions to be answered:

Should I drop the language management in the admin pages and use the language table of the multi-language support of PW2.2? So the module would map the pages associated to the languages set up there?

Ryan, is it possible to extend the $languages var you now implemented to add some functions to make the content translations accessible using the same interface? Would it make sense?

Link to comment
Share on other sites

I see your point. But wouldn’t it also be confusing, if you had to translate the static parts of your templates through the new PW multi-language feature and also had to install the same language again in an translator module to translate the actual content of the pages?

Link to comment
Share on other sites

I think it's still better to split front and backend translations... Because I always want to have the backend in the right language and also want to have the opportunity to change all of the front end language trees without switching backend language. Maybe your plugin just could use the installed backend languages as recommendations for new front end languages...

Link to comment
Share on other sites

First–as it could be a reason for confusion–I’ll change  the modules name from "Languages" to … ehm, not sure yet. Something like "Content Translations".

I didn't realize that you also added a languages API var, sorry for the duplication there. The new $languages API var can also be hooked in many ways, and I'll be glad to add anything to it that would be helpful to you–just let me know. The $languages API var is extended from this class, which is the same one used by users, roles, permissions, etc.

This class: /wire/modules/LanguageSupport/Languages.php

Extends this class: /wire/core/PagesType.php

Each of the languages is actually just Page. If you iterate the $languages API var, you can get all the language pages. There is also $languages->get(...) and $languages->find(...) that function just like the ones from the $pages API var.

Should I drop the language management in the admin pages and use the language table of the multi-language support of PW2.2? So the module would map the pages associated to the languages set up there?

I'm not sure that I have enough background on your module to answer that. But did want to mention that the new LanguageSupport module adds a template called 'language' that is used by each Language Page. You can add fields to that template like any other. So if you were to utilize the PW 2.2 languages with your own, then I'm guessing you would likely add the fields to that 'language' template that your module needs, and then they would be available in all the languages. Again, I'm not sure if this is the right strategy for your module or not, but just wanted to mention that is one of the ways it can built upon.

Ryan, is it possible to extend the $languages var you now implemented to add some functions to make the content translations accessible using the same interface? Would it make sense?

It is. The $languages API var can be extended like anything else. If you wanted to add new functions to it, then you would just add a hook for a function that isn't already there. i.e.

<?php
wire('languages')->addHook('languagesNewMethodName', $this, 'thisClassMethodName'); 

…or…

<?php
$this->addHook('Languages::newMethodName', $this, 'thisClassMethodName'); 

I'm happy to modify or add anything necessary to support the needs of your module – just let me know of any way I can collaborate or be of help.

Link to comment
Share on other sites

Thanks again for your detailled reply here. I think I’ve to play around a bit with the language support thing to find out, if it would make sense to base the module upon your already implemented structures. We’ll see.

Link to comment
Share on other sites

  • 2 weeks later...

I saw this recent post in the forums: http://processwire.com/talk/index.php/topic,770.0.html

It seems, as PW progresses, the same questions pop up in community members' minds at the same time. While working on a proper solution to deal with the multi-language content stuff in future, I have been thinking about the multiple tree thing a lot. I have always been a fan of ModX' "context" idea. But as ryan and Soma already pointed out, PW as a "framework" provides a very open base that allows to "interpret" the given structure as multiple trees, if you want to.

As it could actually be helpful in such a case-or a similar case like when dealing with multiple languages in frontend-to react correspondingly in view of content management, I've lately been thinking about creating a "contexts" module to store context meta information (like e.g. language or maybe content type?), context page access and context switching. All this through a template var $context with acts like $pages but just within the context's page tree.

My idea was to provide this as a standalone module. And the languages module for content translations would extend the context functionality by page mapping between the contexts. So the languages wouldn't be manages separately, as the contexts represented by their particular "home" pages theirselves would be the data containers for any language data.

Link to comment
Share on other sites

Oliver, this sounds interesting. I'm not sure that I understand all of what you are saying (as I don't have that background with Modx) but I think I get most of it. I've also been thinking about domain context and being able to tell PW that if your site is accessed from domain2.com rather than domain1.com, you could direct it towards some branch as the root context of that domain. Though it would be a careful balance, as I think changing the context of the default API vars would be confusing for development. So it would just be changing context for output (or creating new API vars to provide context). The same approach could be used for multi language, as each language lived at it's own hostname (i.e. en.domain.com and fr.domain.com) and could be loaded at the context of /en/ or /fr/ as the root. I don't know how to make this happen yet, so it's just ideas at this stage... 

Only partially related to the above, but I also wanted to mention that you can already use any $page as a context and then set it as a new API var. For instance, you could do this from an autoload module:

Wire::setFuel('mypages', $pages->get('/fr/'));

After that there is a new $mypages API var available to templates and modules and it has a root context of /fr/. So you can do a $mypages->find(...) or $mypages->get(...) just like with $pages, except that the context will always be confined to the /fr/ branch.

Link to comment
Share on other sites

Your last hint is exactly, what I thought of.

A template var $context made from

Wire::setFuel('context', $pages->get('/contexthome/'));

.

But I think it would be necessary to make $context a extended version of $pages with e.g. a ->switch($contextname) method. The context's meta data would-as already mentioned-be stored in fields of the special template of the context's home pages.

BTW: Is there a way to keep template fields from being deleted?

Link to comment
Share on other sites

But I think it would be necessary to make $context a extended version of $pages with e.g. a ->switch($contextname) method.

I think that the only methods in $pages where context would matter are find() and get(). Those methods are also present on every Page object and can perform the same function as they do in $pages. Except they only find() or get() pages from their family tree (children, grandchildren, and so on). So the page you call find() or get() on already has a root context to everything below it, at least that's the intention. Whereas, the intention with the $pages API var is that it encompasses all contexts (like jQuery's $() method). As a result, I'm not sure I totally understand the value of changing the $pages context as it seems like it's duplicating something we already provide.

On the other hand, what I think could add value is providing contexts that can go outside the tree. And maybe this is what you were trying to say before and I didn't understand at first. But I understand code better than writing, so imagine this:

<?php
$context = $pages->context("template=basic-page, modified>=$today"); 

You could provide any selector as the argument to the context() method. The returned $context would be an instance of PagesType that basically provides you with dedicated get() and find() methods that are confined to the context that was provided in the function call. Meaning, any calls to $context->get() or $context->find() already assume the context that was provided when $context was created. Here's an example:

<?php
$news = $pages->context("template=news"); // no pages are loaded from this
$featured = $news->find("featured=1, sort=-date"); // pages are loaded here

While you can already do this (like below), it's not a good idea for big sites for the reason indicated in the code comment:

<?php
$news = $pages->find("template=news"); // ALL news pages loaded from this
$featured = $news->find("featured=1, sort=-date"); // already loaded pages are filtered here

For that reason, I think providing the ability to create new $pages-like API vars that can be confined to any selector-specified context does provide real value in being able to create your own API vars... especially for bigger sites. Is this related to what you were already thinking, or have I gone off on a tangent? :) Here's how I think you'd use it in a multi language situation:

<?php
$languagePages = $pages->context("has_parent=/fr/"); 

The only thing I can't get past is just that the result of the above would really be no different than just getting the /fr/ page and assigning it to $languagePages:

<?php
$languagePages = $pages->get("/fr/"); 

What's different is that $languagePages would be a dedicated PagesType instance in the first example, and just a Page in the second example. But how you would use them and the syntax for subsequent get() and find() calls would be identical... so I'm not sure this particular approach has much value when one only needs a family context, like /fr/. The value seems to come from when you need to take the context beyond that (whether with languages or anything else).

The context's meta data would-as already mentioned-be stored in fields of the special template of the context's home pages.

I'm not sure I understand about the context's metadata? Can you give an example of the metadata?

BTW: Is there a way to keep template fields from being deleted?

So long as a field is attached to a template, it can't be deleted. Do you mean: is there a way to prevent a field from being removed from a template? If so, there is: you can give the field a 'permanent' flag:

$field->flags = $field->flags | Field::flagPermanent; 
$field->save(); 

You can also make a field non-deleteable by giving it a 'system' flag, so that even if it's not used by any templates, it still can't be deleted:

$field->flags = $field->flags | Field::flagSystem; 

If you ever need to remove system or permanent flags, it's intentionally cryptic to remove them (applicable only to system or permanent flags). You can't remove them by just changing the flags or PW will throw an exception. Instead, you have to make your intention clear by first giving it an 'system override' flag:

<?php

// removing the system or permanent bits won't work because PW won't allow it 
$field->flags = $field->flags & ~Field::flagSystem; // exception gets thrown here

// but if you first give it an override flag, PW will let you do it
$field->flags = $field->flags | Field::flagSystemOverride; 
// now you can remove the flags
$field->flags = $field->flags & ~Field::flagSystem; 
$field->flags = $field->flags & ~Field::flagPermanent; 
$field->flags = $field->flags & ~Field::flagSystemOverride;

This information about adding/removing system flags applies to templates and pages too. Though pages use the term 'status' rather than 'flags'.

Link to comment
Share on other sites

Thanks once more for the input. You touched on some aspects I still have to think about. Yeah, I guess things will get clearer as soon as I try to put them into code.

With meta information I meant stuff like context specific URL (to allow context selection by domain) or language information (en-US). The point of introducing an additional template var $context besides accessing the context's subpages, would be to provide the contexts information at any time on any subpage. And to easily switch contexts (from mobile. to www. for example). I'll give it a try. Maybe you are right and there won't be a good reason left in the end for having another template var around.

Link to comment
Share on other sites

  • 3 months later...

Hey Nico, after the multi-language features ryan implemented for backend and static template content, I'm still re-thinking the purpose of my module. The goal is to keep the idea of separate page trees. But basically the new module won‘t be about languages but about contexts the content belongs to. Such a context can - but doesn't have to - be a language. Or a mobile version of your site. You'll be able to define rules like url matching or client language detection to define what page tree to use. An API object var will give you the possibility to switch contexts. And it will be working together with PWs native language management.

The page mapping feature will be provided by an additional module extending the context module by the mapping capabilities you know from language module.

As there is still a lot work to do, I can't tell you yet when I'll be able to push a first stable version. Would need some holidays to focus on this one.

  • Like 3
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...