Hooks and plugin Ext in Liferay portal 6

In summary, the portal (6.0 or above) supports six different types of plugins out-of-the-box. They are portlets, themes, layout templates, webs, hooks, and ext.

  • Portlets: web applications that run in a portion of a web page;
  • Themes : look and feel of pages;
  • Layout Templates : ways of choosing how the portlets will be arranged on a page;
  • Hooks : allow overriding the portal core functionality;
  • Webs : regular Java EE web modules designed to work with the portal, like ESB (Enterprise Service Bus), SSO (Single Sign-On), etc.
  • Ext : use ext environment as a plugin.

Abstracted from the book: Liferay Portal 6 Enterprise Intranets

This article will share two cool features - Hooks and Plugins Ext - in Liferay portal 6. Speciall thanks to Amos Fong, so that we could minimize the ext environment by using hooks. A lot of thanks to Brian Chan , who coined terms "Hook" and "Plugin Ext" and implemented them as part of wonderful features in Liferay portal 6.

Plugin Ext

Ext environment is now available as a plugin.

You would know how to use it (plugin Ext) by an example - hello-world-ext .

Under hello-world-ext/docroot/WEB-INF, you'll see a lot of folders that start with ext-* as shown in following screenshot.

  • ext-impl/src contains code that will override portal-impl/src
  • ext-lib/global is where you put jars that are available in the global class loader
  • ext-lib/portal is where you put jars that are available only to the portal class loader
  • ext-service/src contains code that will override portal-kernel/src and portal-service/src
  • ext-util-bridges/src contains code that will override util-bridges/src
  • ext-util-java/src contains code that will override util-java/src
  • ext-util-taglib/src contains code that will override util-taglib/src
  • ext-web/docroot contains code that will override portal-web.

Note that if you modify ext-web/docroot/WEB-INF/web.xml, those changes are merged into portal-web/WEB-INF/web.xml. ext-web also contains /WEB-INF/*-ext.xml files that are used to override what is in portal-web.

and more ... You could refer to LPS-6341 and Ext environment and the Ext Plugin in 6.x.

As you can see, It works very similarly to that of Ext environment, but it is now much smaller and lighter weight.

Note that support for ServiceBuilder in EXT plugins will be deprecated in future versions. EXT plugins are designed to override the portal's core code that cannot be done with hooks, layout templates, portlets, or themes. EXT plugins are not meant to contain new custom services. Thus migrate your service.xml of 5.x to a portlet plugin.

Open Issues

Of course, it would be nice that the plugin Ext should have same functions and features as that of Ext environment. Actually there are still a set of open issues that should get fixed in future release.The following are some of them.

Hooks

Hooks is a feature to catch hold of the properties and JSP files into an instance of the portal as if catching them with a hook. Hook plugins are more powerful plugins that come to complement portlets, themes, layout templates, and web modules. A hook plugin could be standalone or go with other plugins like portlets, webs, themes. For instance, the portlet so-portlet is a portlet plugin for Social Office with hooks. In general, hooks would be very helpful tools to customize the portal without touching the code part of the portal. In addition, you would use hooks to provide patches for the portal systems or social office products. 

Abstracted from the book: Liferay Portal 6 Enterprise Intranets (coming out soon)

Setup

In general, there are four kinds of hook parameters: portal-properties (called portal properties hooks), language-properties (called language properties hooks), custom-jsp-dir (called custom JSPs hooks) and service (called portal service hooks) as specified in $PORTAL_ROOT_HOME/dtd/liferay-hook_6_0_0.dtd.

<!ELEMENT hook (portal-properties?, language-properties*, custom-jsp-dir?, service*)>
<!ELEMENT portal-properties (#PCDATA)>
<!ELEMENT language-properties (#PCDATA)>
<!ELEMENT custom-jsp-dir (#PCDATA)>
<!ELEMENT service (service-type, service-impl)>
<!ELEMENT service-type (#PCDATA)>
<!ELEMENT service-impl (#PCDATA)>

As shown in the preceding code, the ordering of elements is significant in the DTD (Document Type Definition) - you need to have your portal properties (only one marked by ?), language properties (could be many marked by *), custom-jsp-dir (only one marked by ?) and service (could be many marked by *) declared in the same order.

Language properties hooks allow us to install new translations or override few words in existing translations. JSP hooks provide a way to easily modify JSP files without having to alter the core of the portal, whereas portal properties hooks allow runtime re-configuration of the portal. Portal service hooks provide a way to easily override portal services. The portal configuration properties can be altered by specifying an override file, where the properties will immediately take effect when deployed.

Note that not all portal properties can be overridden via a hook. The supported properties are:

auth.forward.by.last.path
auto.deploy.listeners
application.startup.events
auth.failure
auth.max.failures
auth.pipeline.post
auth.pipeline.pre
auto.login.hooks
captcha.check.portal.create_account
control.panel.entry.class.default
default.landing.page.path
dl.hook.impl
field.enable.com.liferay.portal.model.Contact.birthday
field.enable.com.liferay.portal.model.Contact.male
field.enable.com.liferay.portal.model.Organization.status
hot.deploy.listeners
image.hook.impl
javascript.fast.load
layout.static.portlets.all
layout.template.cache.enabled
layout.user.private.layouts.auto.create
layout.user.private.layouts.enabled
layout.user.private.layouts.modifiable
layout.user.public.layouts.auto.create
layout.user.public.layouts.enabled
layout.user.public.layouts.modifiable
ldap.attrs.transformer.impl
login.create.account.allow.custom.password
login.events.post
login.events.pre
logout.events.post
logout.events.pre
mail.hook.impl
my.places.show.community.private.sites.with.no.layouts
my.places.show.community.public.sites.with.no.layouts
my.places.show.organization.private.sites.with.no.layouts
my.places.show.organization.public.sites.with.no.layouts
my.places.show.user.private.sites.with.no.layouts
my.places.show.user.public.sites.with.no.layouts
passwords.passwordpolicytoolkit.generator
passwords.passwordpolicytoolkit.static
servlet.session.create.events
servlet.session.destroy.events
servlet.service.events.post
servlet.service.events.pre
session.phishing.protected.attributes
terms.of.use.required
theme.css.fast.load
theme.images.fast.load
upgrade.processes
users.email.address.generator
users.email.address.required
users.full.name.validator
users.screen.name.always.autogenerate
users.screen.name.generator
users.screen.name.validator
value.object.listener.*

What’s happening?

As you can see, hooks can be standalone plugins, where one XML file is required liferay-hook.xml. Or hooks can work together with portlets, where simply adding one XML file liferay-hook.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.0.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_0_0.dtd">

<hook>
    <portal-properties>portal.properties</portal-properties>
    <language-properties>content/Language_en.properties</language-properties>
    <custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
</hook>

Portal Properties Hooks

Through portal properties hooks, we could change certain configuration properties dynamically and inject behaviour into the hooks defined in the portal.properties file. All of the hooks that we have discussed above will revert, and their targeted functionality will be disabled immediately as soon as they are un-deployed from the portal. Also, each type of hook can easily be disabled via the portal.properties file.

Note that a portal.properties file must exist in the plugin hook's WEB-INF/classes folder if portal properties hooks are enabled. Plugin hooks can override the properties like dl.hook.impl, mail.hook.impl, image.hook.impl, etc. To override these properties, add these properties to a portal.properties file in the plugin hook's WEB-INF/classes folder.

Language Properties Hooks

Language properties hooks allow us to install new translations or override few words in existing translations. For example, you’re going to rename “Custom Attributes” as “Custom Fileds” in user editing mode or organization editing mode. You can create and folder content under plugin hook’s WEB-INF/classes, and then you could create a properties file Language_en.properties under the plugin hook's WEB-INF/classes/content. Finally, add following line at Language_en.properties.

custom-attributes=Custom Fields

The above code shows that the message key custom-attributes will have display text Custom Fields.
Note that a Language_en.properties file must exist in the plugin hook's WEB-INF/classes/content folder if language properties hooks got enabled.

More interestingly, language properties hooks allow us to install new translations or override few words in existing translations in both a single language and multiple languages. You can specify multiple language properties files at liferay-hook.xml. Therefore, multiple language properties files got supported via hooks. It is a nice feature that multiple language properties files got supported via hooks.

Custom JSPs Hooks

Custom JSP hooks provide a way to easily modify JSP files of the portal without having to alter the core of the portal. A folder /META-INF/custom_jsps must exist in the plugin hook's Root folder if language properties hooks are enabled.

Under the folder /META-INF/custom_jsps, same folder structure like html as that of $PORTAL_ROOT_HOME/html will be used to override portal JSP files with custom JSP files. In runtime, the original JSP like ${name}.jsp or ${name}.jspf will be renamed as ${name}.portal.jsp or ${name}.portal.jspf under $PORTAL_ROOT_HOME/html; custom JSP files ${name}.jsp or ${name}.jspf will get copied to the folder $PORTAL_ROOT_HOME/html.

For example, you’re going to override the view of login portlet. You can put custom JSP file login.jsp of hook plugin at /META-INF/custom_jsps/html/portlet/login. In runtime, the portal will rename the original JSP login.jsp as login.portal.jsp under $PORTAL_ROOT_HOME/html/portlet/login first; and then the portal will copy custom JSP files login.jsp of hook plugin at /META-INF/custom_jsps/html/portlet/login to the folder $PORTAL_ROOT_HOME/html/portlet/login. More interestingly, you can again include renamed original JSP as follows in custom JSP files login.jsp of hook plugin at /META-INF/custom_jsps/html/portlet/login.

<liferay-util:include page="/html/portlet/login/login.portal.jsp" />

Therefore after deploying hook plugin, you would be see both login.jsp and login.portal.jsp under the folder $PORTAL_ROOT_HOME/html/portlet/login.

Portal Service Hooks

Portal service hooks allow us to customize portal services and models. That is, plugin hooks can override services and models. For example, to override UserLocalService, you can add the following in liferay-hook.xml.
<hook>
 <service>
  <service-type>com.liferay.portal.service.UserLocalService</service-type>
  <service-impl>com.ext.hook.service.impl.ExtUserLocalServiceImpl</service-impl>
 </service>
</hook>

As shown in above code, service was specified by tags <service-type> and <service-impl>. The tag <service-type> provides the original service or model in the portal; and the tag <service-impl> provides customize portal service or model, which will override the original service or model in the portal. More interestingly, you would able to specify many tags <service> if in need.

Note that portal service hooks, portal properties hooks and language properties hooks will get inactive when Hook plugins were un-deployed. 

Enhancement

Custom JSP hooks provide a way to easily modify JSP files of the portal without having to alter the core of the portal. In runtime, the original JSP like ${name}.jsp or ${name}.jspf will be renamed as ${name}.portal.jsp or ${name}.portal.jspf under $PORTAL_ROOT_HOME/html. When hook plugin got un-deployed, the original JSPs should get rolled back. For example, considering custom JSP on logon view, when hook plugin got un-deployed, the portal should delete the JSP login.jsp under $PORTAL_ROOT_HOME/html/portlet/login, and rename login.portal.jsp as login.jsp under $PORTAL_ROOT_HOME/html/portlet/login. This is a nice feature that we could be able to restore the original JSPs of the portal when hook plugin got un-deployed.

And what happens when you deploy two hooks that override the same JSP file? It's currently unsupported for hooks to change the same JSP file. You need to check the hooks for collision; you can use a separate repository for handling this. If you have a collision, the last deployed JSP will be used, but it should be done with care as the order of deployment is not guaranteed.   The hooks should be smart to handle it – this feature is highly expected, too.

As you can see, there are five different kinds of plugins: portlet, theme, layout template, web and ext. For more details, refer to chapter 12 Search, WAP, CRM, Widgets, Reporting and Auditing of the book: Liferay Portal 6 Enterprise Intranets . Ideally one plugin should contain only one kind of plugin like web, ext, theme, hook, and layout template. But the plugin portlet can contain many portlets plus hook optionally, for example the plugin so-portlet included several portlets and a hook. 

Blogs
Sir, Thank You for detailed explanations about Hooks and Plugin Ext.
Hi Jonas, do you know whether Brian plans to implement the additional features in the Ext Plugin to make it more usable?
Hi Tomas, thank you.

You may refer to ticket LPS-6341 and message post - Ext environment and the Ext Plugin in 6.x - at http://www.liferay.com/community/forums/-/message_boards/message/4533829. Hope that it helps.
I'm interested on buying the book... but I'm also waiting the version 6 of the portal to be released... Do you know if the book still aplies to the new portal or if there will be a new version of the book?

Thanks!!!
Ups!!! Wrong comment!!! I'm sorry I was reading this (http://www.liferay.com/web/jonas.yuan/blog?p_p_id=33&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&_33_struts_action=/blogs/view) blog and I don't know how I ended up here... I'm terribly sorry :S

Greetings!!!
Hi Jaime, thank you. The new book (Liferay Portal 6 Enterprise Intranets) would be ready in two weeks. You would get updated at the blog - http://www.liferay.com/web/jonas.yuan/blog/-/blogs/liferay-book%3A-liferay-portal-6-enterprise-intranets
"you could create a properties file Language_en.properties under the plugin hook's WEB-INF/classes/content. Finally, add following line at Language_en.properties"

Does this apply to Language_en.properties.native as well? If I don't want to change Language_xx.properties but Language_xx.properties.native, should I just copy the native file in the content folder and override it? Shouldn't I add anything extra to the liferay-hook.xml file for my .native file?
ok I tried both these methods and it does not work. I copied Language_fa.properties.native and Language_fa.properties files in the content folder of my hook (WEB-INF\content), changed some translations in the Language_fa.properties.native file and deployed, and no change was made. I then added
<language-properties>content/Language_fa.properties.native</language-properties>
in addition to
<language-properties>content/Language_fa.properties</language-properties>
and deployed, and still no changes.

Anyone any ideas?
(I am using lp6-tomcat bundle under Windows with Mysql5.1)
I have the same problem, the language hook doesn't work after version 6.
Hi Rex,

I already submitted an issue for this:
http://issues.liferay.com/browse/LPS-10308

I hope somebody would reply from the staff.
Hi Puj Z,

According to this article: http://www.liferay.com/community/wiki/-/wiki/Main/Ext+Plugin , I use the ext-impl to replace the language properties files, and it works.
@Rex and @Puj, thank you. Ext environment is now available as a plugin. That is, you can do the same in ext plugin as that you can do in ext environment. If not, it is a bug.

Thank you Puj to report bugs.

By the way, it is working well for me (real example - Knowledge base portlet).

<hook>
<portal-properties>portal.properties</portal-properties>
<language-properties>content/Language_en_US.properties</language-properties>
</hook>

you may have to use (locale, country code, etc.):

"Language_en_US.properties"
I tried the following to no effect:

<hook>
<portal-properties>portal.properties</portal-properties>
<language-properties>content/Language_sl_SI.properties</language-properties>
<language-properties>content/Language_sl_SI.properties.native</language-properties>
</hook>

It builds and deploys in 6.0.2 without error, but to no effect.
It turned out I had errors in the properties file ... it works, at least it does in 6.0.5.

<hook>
<portal-properties>portal-ext.properties</portal-properties>
<language-properties>content/Language_sl.properties</language-properties>
</hook>
Hello Yuan,

As a follower of your book Systems Development, now I miss having new extlet environment in eclipse.

How can we have the created my-ext in eclipse ? I tried to import it as a file system into an existing empty Java project, but the folders that doesnt have any file are not imported...

Can you please provide any comment or a wiki about having new ext in eclipse environment...

ilke
Hi Ilke, Thanks.

You may take following steps to build a new ext in eclipse.
1) create a java project called name like "ext528";
2) unzip ext zip to the folder "ext528"; (ignore conflicts)
3) refresh the project in eclipse.

Hope that it helps,

Jonas
I am trying to implement -ext plugin through the IDE but think the problem I have is related to the ext plugin in general.
where should we edit the portal-ext.properties (to declare new organization types, terms of use etc).
trying to use the 5.2 System devt book but within 6.0.4 portal - so I know there are some changes.

BTW will there be a 6.0 equivalent or at least 6.0 version of the sample code for the book?
Hi Dave,

if you are using the portal for the first time you have to create portal-ext.properties yourself in this address:
$tomcat-6.0.26\webapps\ROOT\WEB-INF\classes

Whetever you write in this file, will be overwritten on the portal.properties after you restart you tomcat.

There are some properties that you can change in the portal-ext.properties of your hook, so that you wouldn't have to retart tomcat.
Hi Dave, the approach is same for both 6.0 and 5.2; and the code should be different.

Did you get the issue resolved?
Hi Jonas,
Currently moved on to writing a hook to add to Calendar service. I have just done a dif between the SDK 6.0.4 and new 6.0.5 and notice the ext build file is significantly changed so perhaps this is cleared now.

Are you doing an version of the code from the book for 6.0?
or even a new book?

Thanks for checking !
Hi Dave, Thanks. I am planning to release a new book (Liferay 6 User Interface Development) this year and to release another new book (Liferay Portal 6.1 Systems Development) next year when applicable.

Your comments or suggestions would be helpful

Jonas
Hi,

I tried to overwrite e-mail templates (.tmpl files in \com\liferay\portlet\admin\dependencies) by using hooks but it is not working. What I did was I created the same path and put new .tmpl files there and then deployed the hook.

Is it an issue or do we have to use ext to change such templates?
Any helps would be appreciated! emoticon
Hi Puj, you can double check custom templates file first; and then double check your deployment of new .tmpl files.

Please show your testing results here if applicable. Thanks.
Hi Jonas,

can you clarify what's the difference between a web plugin and develop a normal web application deployed in the same application server as a war?

In my case I need to add add an external web service to a an existing Liferay portal on Tomcat. Do you suggest using a web plugin or is more convenient develop an external web apps with axis?

Thanks,
Denis.
Hi Denis, Thanks.

Webs or called Web Application Integrator (WAI) will automatically deploy any standard Java servlet application as a portlet within Liferay. To use the WAI, you can simply copy an application WAR file into the auto-deploy directory, and then add the portlet to your page.

Webs are similar or most same to normal web applications in general. But you can add more in Webs.

For your requirements (an external web service), you can leverage portlets, for example, alfresco-content portlet - it consume web services and RESTful service of Alfresco.

Hope that it helps.
Hi Jonas,

thanks for your answer. I add also Ray answer that can help others: "You can do either, since a liferay web-plugin is any pure webapp
where all we add is some very thin layer where we can provide
checking for dependencies (say if your webapp depends on some
portal-plugin service for instance, you can declare through
configuration of the web-plugin that it depends on some other
portal-plugins).

A web-plugin also add support for embedding hook definition or
Service Builder services within a plain old webapp. And finally,
you can deploy them using the liferay autodeploy mechanism the same
way that you do with other portal-plugins (drop in the deploy folder
or upload through the UI). I'm sure there might be one or two small
things I missed, but essentially, if you don't care about any of the above,
go ahead and make you webapp a plain old webapp."
Hi Jonas,

sorry for my late reply, I finally got the time to test this again and it is still not working.
Overwriting .tmpl files (in dependencies folder) is apparently not possible with hook. I don't know if it is an issue or it is just not supposed to work.
I would be nice if it would have worked with hooks, because now for every change in email templates (if the gui is not available for changing templates, like in invitation portlet) we have to write an ext?!

Thanks for your consideration,
Puj
Hi Puj,
it simply isn't possible to overwrite .tmpl files using hook. You need Ext (Plugin) because they are saved together with Liferay classes inside portal-impl.jar and loaded by web-app class loader. It could work if you reload them in the app. server's class loader, but hook can't do it.
Thank you, Tomas. Hooks only support:

1) portal properties (only one marked by ?),
2) language properties (could be many marked by *),
3) custom-jsp-dir (only one marked by ?) and
4) service (could be many marked by *) declared in the same order.

Hi Puj, you may overwrite .tmpl in $PORTAL_ROOT_HOME/WEB-INF/classes and resetting portal-ext.properties. There are a few examples in the book: Liferay Portal 6 Enterprise Intranets.
Hi Jonas,

I wasn't so sure where to paste this suggestion to Liferay. However, since you have this blog on hooks, I felt I should put it here as a comment/suggestion.

1. I find hooks very useful for adapting the portal to some specific circumstances. For example, I am using hooks to implement some extra validations before adding, updating or deleting a user, organization, role as well as custom attributes. This fundamentally requires my hooking the corresponding portal services which I have done successfully.

2. However, I encounter the following limitation which I think should be addressed in order to make hooks more useful while avoiding the need to tamper with Liferay codes. viz:

- The fact that PortletClass is not hookable creates a vacuum between the service hook layer and custom jsp hook layer. For example, I cannot throw new types of exceptions (e.g. due to my custom validations error results) at the service hook layer without having to adjust the Portlet Class (or ActionUtil.java used by the Portlet Class) so as to handle those exceptions. This will logically imply tampering with non-hookable codes which is not neat.

3. While acknowledging that making PortletClass hookable may be more challenging, I think that having a generic CustomException("custom-error-message") class always throwable at the service layer could be useful.

e.g. public void deleteColumn(long arg0) throws PortalException, SystemException, CustomException{
...
}

Such CustomException logically should always be caught at the Portlet Class layer in case it is thrown at the service hook layer. Handling at the Portlet Class layer involves adding the error message associated with it to SessionErrors. i.e.
...
}catch(CustomException ce)
SessionErrors.add(request, ce.toString()).
}
...
3. If this is done, developers (like myself - I am in love with Liferay development) can throw their CustomException message at the service hook layer and display at the custom-jsp hook layer i.e.

<liferay-ui:error key="custom-error-message" message="custom-error-message-language-util-key" />

knowing very well that at the non-hookable PortletClass layer, CustomException will always be caught and message added to SessionErrors if any.
Hi Pius, Thanks. Good idea, indeed. Let me check this and come back to you shortly.
Hi,

I am writing a listener and I want to import some services such as
com.liferay.portal.service.ServiceContextThreadLocal; or
com.liferay.portal.security.ldap.LDAPUserTransactionThreadLocal;

, I add the classpaths to eclpise and it recognizes the classes, however at the time of deploying I get:
1cannot find symbol
2 [javac] symbol : class LDAPUserTransactionThreadLocal

should anything extra be done in a hook in order to use liferay services? I have no problem importing models.

Down anyone know what I should do in order to to be able to deploy successfully? Copying the classes in my classes directory might be a solution but is not a clean idea.
I need to use service builder in liferay 6 ext plugins and it seems impossibile. There is a warning that says "WARNING: Support for ServiceBuilder in EXT plugins will be deprecated in future versions. EXT plugins are designed to override the portal's core code that
cannot be done with hooks, layout templates, portlets, or themes. EXT plugins
are not meant to contain new custom services. Please migrate your service.xml to
a portlet plugin."

This means that in the current version is possible to use service builder and not in the future, but it doesn't work: I seen that in the ext build.xml file (and nested) the build-service is not the same I find in the plugin portlet build.xml file.

I need to georeference and date assets so I think the best place is in ext but I need service builder to prepare the additional tables to store georeference and date values...
Hi Alex, Thanks.

Note that support for ServiceBuilder in EXT plugins will be deprecated in future versions. EXT plugins are designed to override the portal's core code that cannot be done with hooks, layout templates, portlets, or themes. EXT plugins are not meant to contain new custom services. Thus migrate your service.xml of 5.x to a portlet plugin.
Dear Sir,

I'm using Liferay Portal Community Edition 6.0.5 Bundled with GlassFish Server Open Source Edition 3.0.1. I´ve got a problem with my languages hook: changes are always not applied.

My liferay-hook.xml:
<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_1_0.dtd">
<hook>
<portal-properties>portal-ext.properties</portal-properties>
<language-properties>content/Language_zh_CN.properties</languageproperties>
<language-properties>content/Language_zh_TW.properties</languageproperties>
<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
</hook>

Please help.

Best Regards and Have a Nice Day

Chen Yi Chen
Hi Chenyi, you may use "6.0.0" and "*_6_0_0" for 6.0.5 / 6.0.*.

For example:

<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.0.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_0_0.dtd">

<hook>
<portal-properties>portal.properties</portal-properties>
<language-properties>content/Language_en.properties</language-properties>
<language-properties>content/Language_en_GB.properties</language-properties>
<language-properties>content/Language_de.properties</language-properties>
<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
</hook>

Hope that it helps,

Thanks

Jonas Yuan
Dear Sir,

Thank you very much for your reply!

Unfortunately, changes are still not applied.

My liferay_zh_TW_lang_hook.war is attached at ticket LPS-27372. Besides, I fond "French translations 1.0" Community Plugins at the following URL:
http://www.liferay.com/downloads/liferay-portal/community-plugins/-/software_catalog/products/6830833?_98_redirect=http%3A%2F%2Fwww.liferay.com%2Fdownloads%2Fliferay-portal%2Fcommunity-plugins%2F-%2Fsoftware_catalog%2Fproducts%3F_98_keywords%3Dtranslations%26_98_page%3D1%26_98_type%3D%26_98_tabs1TabsScroll%3D%26_98_itemsPerPage%3D20

I suppose that "French translations 1.0" language hook should be work, but I have no idea why my liferay_zh_TW_lang_hook.war language hook isn't work.

Please help me to clarify the aforementioned issues I have encountered, thank you very much!

Best Regards and Have a Nice Day

Yi Chen, Chen
Hi Yi,

Try updating the liferay-hook.xml file to this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.0.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_0_0.dtd">

<hook>
<language-properties>content/Language_zh_TW.properties</languageproperties>
</hook>

Then add a "Language_zh_TW.properties" file to the .\WEB-INF\src\content directory.
When I use `ant clean-app-server`, `ant direct-deploy` to deploy my ext plugins example, it gives me error like this. Is there anybody knows the answer? Thank you very much!
17:38:05,736 ERROR [http-bio-8080-exec-1][status_jsp:665] org.apache.jasper.JasperException: An exception occurred processing JSP page /html/portal/layout/view/portlet.jsp at line 67

64: </c:if>
65:
66: <%
67: RuntimePortletUtil.processTemplate(application, request, response, pageContext, out, velocityTemplateId, velocityTemplateContent);
68: }
69: %>
70:


Stacktrace:
org.apache.jasper.JasperException: An exception occurred processing JSP page /html/portal/layout/view/portlet.jsp at line 67

64: </c:if>
65:
66: <%
67: RuntimePortletUtil.processTemplate(application, request, response, pageContext, out, velocityTemplateId, velocityTemplateContent);
68: }
69: %>
70:


Stacktrace:
at org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:568)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:470)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
...more